package dsx.log;

import dsx.config.Config;
import dsx.config.ConfigItem;
import dsx.macro.StringTool;
import dsx.system.DateFormat;
import dsx.system.ThreadEx;

import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.io.File;

public class DsxLog {

    static final int MaxCacheNum = 10000;
    static final int MaxMsgLen = 100000;

    static final int None = 0;
    static final int Console = 1;
    static final int ToFile = 2;
    static final int Both = 3;

    static _LogType rootType = null;
    static ConcurrentHashMap<String, _LogType> typeMap = null;
    static LogWriter logWriter = null;
    static Queue<_Log> logQueue = new LinkedList<_Log>();

    static boolean initialized;

    public static void start() {
        synchronized (logQueue) {
            if (!initialized) {
                initRootType();
                initLogType();
                logWriter = new LogWriter();
                logWriter.setInterval(1000);
                logWriter.start();
                initialized = true;
            }
        }
    }

    public static void stop() {
        synchronized (logQueue) {
            if (initialized) {
                if (logWriter != null)
                    logWriter.stopEx();
                logWriter = null;
                logQueue.clear();
                initialized = false;
            }
        }
    }

    private static void initRootType() {
        // 通用设置
        rootType = new _LogType();
        ArrayList<ConfigItem> items = Config.get("console");
        for (ConfigItem item : items) {
            if (item.value.isEmpty())
                continue;
            try {
                switch (item.name) {
                    case "mode":
                        rootType.setMode(Integer.parseInt(item.value));
                        break;
                    case "fileMode":
                        rootType.setFileMode(Integer.parseInt(item.value));
                        break;
                    case "maxFileSize":
                        String str = item.value;
                        int size = 0;
                        if (str.endsWith("MB")) {
                            size = Integer.parseInt(str.substring(0, str.length() - 2)) * 1024 * 1024;
                            rootType.setMaxFileSize(size);
                        } else if (str.endsWith("KB")) {
                            size = Integer.parseInt(str.substring(0, str.length() - 2)) * 1024;
                            rootType.setMaxFileSize(size);
                        } else if (str.endsWith("B")) {
                            size = Integer.parseInt(str.substring(0, str.length() - 1));
                            rootType.setMaxFileSize(size);
                        } else {
                            size = Integer.parseInt(str);
                            rootType.setMaxFileSize(size);
                        }
                        break;
                    case "logRoot":
                        rootType.setLogRoot(item.value);
                        break;
                    case "consoleLimit":
                        rootType.setConsoleLimit(Integer.parseInt(item.value));
                        break;
                }
            } catch (Exception ex) {
            }
        }
    }

    private static void initLogType() {
        // 指定日志类型的专用设置
        typeMap = new ConcurrentHashMap<String, _LogType>();
        ArrayList<ArrayList<ConfigItem>> types = Config.getSections("logType");
        _LogType logType = new _LogType();
        for (ArrayList<ConfigItem> items : types) {
            String typeNames = "";
            for (ConfigItem item : items) {
                if (item.value.isEmpty())
                    continue;
                try {
                    switch (item.name) {
                        case "typeName":
                            typeNames = item.value;
                            break;
                        case "mode":
                            logType.setMode(Integer.parseInt(item.value));
                            break;
                        case "fileMode":
                            logType.setFileMode(Integer.parseInt(item.value));
                            break;
                        case "maxFileSize":
                            String str = item.value;
                            int size = 0;
                            if (str.endsWith("MB")) {
                                size = Integer.parseInt(str.substring(0, str.length() - 2)) * 1024 * 1024;
                                logType.setMaxFileSize(size);
                            } else if (str.endsWith("KB")) {
                                size = Integer.parseInt(str.substring(0, str.length() - 2)) * 1024;
                                logType.setMaxFileSize(size);
                            } else if (str.endsWith("B")) {
                                size = Integer.parseInt(str.substring(0, str.length() - 1));
                                logType.setMaxFileSize(size);
                            } else {
                                size = Integer.parseInt(str);
                                logType.setMaxFileSize(size);
                            }
                            break;
                        case "logRoot":
                            logType.setLogRoot(item.value);
                            break;
                        case "consoleList":
                            String[] entityList = item.value.split(",");
                            for (int i = 0; i < entityList.length; i++)
                                logType.getConsoleList().add(entityList[i]);
                            break;
                        case "consoleLimit":
                            rootType.setConsoleLimit(Integer.parseInt(item.value));
                            break;
                    }
                } catch (Exception ex) {
                }
            }
            if (!StringTool.isTrimEmpty(typeNames)) {
                String[] typeNameArr = typeNames.split(",");
                for (String typeName: typeNameArr) {
                    if (!StringTool.isTrimEmpty(typeName)) {
                        _LogType temp = logType.copy();
                        temp.setTypeName(typeName.trim());
                        typeMap.put(temp.getTypeName(), temp);
                    }
                }
            }
        }
    }

    public static void writeLog(String logMsg, String logType, String entityName, String logLevel) {
        try {
            if (logQueue.size() > MaxCacheNum) {
                ThreadEx.sleepEx(15);
                if (logQueue.size() > MaxCacheNum) {
                    logError("log queue full");
                    return;
                }
            }

            String msg;
            if (logMsg.length() > MaxMsgLen) {
                msg = logMsg.substring(0, MaxMsgLen);
            } else {
                msg = logMsg;
            }
            if (typeMap.containsKey(logType))
                msg = String.format("[%s],<%s>,%s", DateFormat.now("yyyy-MM-dd HH:mm:ss"), logLevel, msg);
            else
                msg = String.format("[%s],<%s>,<%s>,%s", DateFormat.now("yyyy-MM-dd HH:mm:ss"), logLevel, entityName, msg);
            synchronized (logQueue) {
                logQueue.offer(new _Log(logType, entityName, logLevel, msg));
            }
            logWriter.notifyEx();
        } catch (Exception ex) {
        }
    }

    private static _LogType getLogType(String logType) throws Exception {
        if (typeMap.containsKey(logType))
            return typeMap.get(logType);
        else
            return rootType;
    }

    private static File getFile(_LogType logType, String entityName) throws Exception {
        String pathName = logType.getLogRoot();
        if (!pathName.endsWith(File.separator))
            pathName = pathName + File.separator;
        if (!logType.getTypeName().isEmpty())
            pathName = pathName + logType.getTypeName();
        else
            pathName = pathName + "_console";
        if (!pathName.endsWith(File.separator))
            pathName = pathName + File.separator;
        if (entityName != null && !entityName.isEmpty())
            pathName = pathName + entityName + File.separator;

        File path = new File(pathName);
        if (!path.exists()) {
            if (!path.mkdirs())
                return null;
        }

        String fileName = entityName;
        if (fileName.isEmpty() && logType.getTypeName().isEmpty())
            fileName = "dsxlog";
        else
            fileName = logType.getTypeName();
        if (logType.getFileMode() == 1)
            fileName = fileName + DateFormat.now("_yyyy-MM-dd");
        String fullFileName = pathName + fileName;
        File file = new File(fullFileName);
        if (!file.exists()) {
            if (file.createNewFile())
                return file;
            else
                return null;
        } else {
            if (file.length() >= logType.getMaxFileSize() && logType.getMaxFileSize() > 0) {
                String nextCurFileName = getNextCutFileName(path, fileName);
                file.renameTo(new File(pathName + nextCurFileName));
                if (!file.createNewFile())
                    return null;
            }
            return file;
        }
    }

    private static String getNextCutFileName(File path, String fileName) {
        int index = 1;
        while (true) {
            String nextCutFileName = fileName + "_" + index;
            boolean exist = false;
            for (File tmpFile : path.listFiles()) {
                if (tmpFile.getName().equals(nextCutFileName)) {
                    exist = true;
                    break;
                }
            }
            if (exist)
                index++;
            else
                return nextCutFileName;
        }
    }

    private static void saveToFile(_LogType logType, _Log log) throws Exception {
        File file = getFile(logType, log.getEntity());
        if (file == null)
            return;
        FileOutputStream fs = new FileOutputStream(file, true);
        fs.write((log.getMsg() + "\r\n").getBytes());
        fs.close();
    }

    private static void logError(String errMsg) {
        try {
            String errFileName = rootType.getLogRoot();
            if (!errFileName.endsWith(File.separator))
                errFileName = errFileName + File.separator;
            errFileName = errFileName + "log_error";
            String msg = String.format("[%s],%s", DateFormat.now("yyyy-MM-dd HH:mm:ss"), errMsg);

            File file = new File(errFileName);
            if (file == null)
                return;
            FileOutputStream fs = new FileOutputStream(file, true);
            fs.write((msg + "\r\n").getBytes());
            fs.close();
        } catch (Exception ex) {

        }
    }

    private static void consoleOut(_LogType logType, _Log log) throws Exception {
        String tmp = log.getMsg();
        if (!logType.getConsoleList().isEmpty() && !log.getEntity().isEmpty()) {
            if (!logType.getConsoleList().contains(log.getEntity()))
                return;
        }

        if (logType.getConsoleLimit() > 0 && tmp.length() > logType.getConsoleLimit())
            tmp = tmp.substring(0, logType.getConsoleLimit());
        System.out.println(tmp);
    }

    private static void handleLog(_Log log) throws Exception {
        _LogType logType = getLogType(log.getType());
        if (logType == null)
            return;
        switch (logType.getMode()) {
            case None:
                break;
            case Console:
                consoleOut(logType, log);
                break;
            case ToFile:
                saveToFile(logType, log);
                break;
            case Both:
                consoleOut(logType, log);
                saveToFile(logType, log);
                break;
        }
    }

    private static class LogWriter extends ThreadEx {

        @Override
        protected void doInLoop() {
            while (true) {
                try {
                    _Log log = null;
                    synchronized (logQueue) {
                        log = logQueue.poll();
                    }
                    if (log != null)
                        handleLog(log);
                    else
                        return;
                } catch (Exception ex) {
                    logError(ex.getMessage());
                    if (logQueue.isEmpty())
                        return;
                }
            }
        }
    }

    private static class _Log {
        String type;
        String entity;
        String level;
        String msg;

        public _Log(String type, String entity, String level, String msg) {
            this.type = type;
            this.entity = entity;
            this.level = level;
            this.msg = msg;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getEntity() {
            return entity;
        }

        public void setEntity(String entity) {
            this.entity = entity;
        }

        public String getLevel() {
            return level;
        }

        public void setLevel(String level) {
            this.level = level;
        }

        public String getMsg() {
            return msg;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }
    }


}
